﻿using System.ComponentModel;
using System.Reflection;

using zijian666.AnyExtensions;
using zijian666.ConvertExtensions;

namespace zijian666.Updater;
partial class LocalData : IEnumerable<KeyValuePair<string, string>>
{
    /// <summary>
    /// 表示额外数据的结构体。
    /// </summary>
    /// <param name="Name">键名</param>
    /// <param name="RawString">原始文本</param>
    private readonly record struct DataValue(string Name, string RawString)
    {
        /// <summary>
        /// 是否只读
        /// </summary>
        public bool IsReadOnly { get; init; } = false;
        /// <summary>
        /// 是否为系统内置字段
        /// </summary>
        public bool IsSystem { get; init; } = false;
        /// <summary>
        /// 描述信息
        /// </summary>
        public string? Description { get; init; } = null;
        /// <summary>
        /// 处理后的值
        /// </summary>
        public string? Value { get; init; } = null;

        /// <summary>
        /// 用于处理值的函数
        /// </summary>
        public Func<string, Task<string>>? Parser { get; init; }
    }

    protected async ValueTask LoadData(IDictionary<string, string> data, DataLoadMode mode = DataLoadMode.Merge)
    {
        if (mode is DataLoadMode.Clear)
        {
            _data.Clear();
            mode = DataLoadMode.Merge;
        }
        foreach (var (key, value) in data)
        {
            if (_data.TryGetValue(key, out var existing))
            {
                if (mode is DataLoadMode.Update or DataLoadMode.Merge)
                {
                    _data[key] = new DataValue(key, value);
                }
            }
            else if (mode is DataLoadMode.Add or DataLoadMode.Merge)
            {
                _data[key] = new DataValue(key, value);
            }
        }

        // 处理 VersionData 中属性的默认值和只读属性
        foreach (var prop in this.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public))
        {
            if (prop.GetIndexParameters().Length > 0)
            {
                continue;
            }
            var desc = prop.GetCustomAttribute<DescriptionAttribute>()?.Description;
            if (_data.TryGetValue(prop.Name, out var item))
            {
                _data[item.Name] = item with
                {
                    IsReadOnly = prop.SetMethod is null or { IsPublic: false },
                    Description = desc,
                    IsSystem = prop.GetSetMethod(true) is null,
                };
            }
            else
            {
                var value = prop.GetCustomAttribute<DefaultValueAttribute>()?.Value?.ToString()
                            ?? prop.GetValue(this)?.ToString()
                            ?? string.Empty;
                _data[prop.Name] = new DataValue(prop.Name, value)
                {
                    IsReadOnly = prop.SetMethod is null or { IsPublic: false },
                    Description = desc,
                    IsSystem = prop.GetSetMethod(true) is null,
                };
            }

            var parsers = prop.GetCustomAttributes().OfType<IParseValue>();
            foreach (var parser in parsers)
            {
                _data[prop.Name] = _data[prop.Name] with
                {
                    Parser = item.Parser.Link(parser.Parse)
                };
            }
        }

        // 处理 RawText -> Value
        foreach (var key in _data.Keys)
        {
            var item = _data[key];
            _data[key] = item with
            {
                Value = GetParsedValue(key)
            };
        }

        // 反写属性
        foreach (var prop in this.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
        {
            if (prop.GetIndexParameters().Length > 0
                || prop.SetMethod is null
                || !_data.TryGetValue(prop.Name, out var item))
            {
                continue;
            }

            if (prop.PropertyType == typeof(DirectoryInfo))
            {
                var path = Path.Combine(CD, item.Value ?? item.RawString);
                var dir = new DirectoryInfo(path);
                prop.SetValue(this, dir);
            }
            else if (prop.PropertyType == typeof(FileInfo))
            {
                var path = Path.Combine(CD, item.Value ?? item.RawString);
                var file = new FileInfo(path);
                prop.SetValue(this, file);
            }
            else
            {
                prop.SetValue(this, Converts.ChangeType(item.Value ?? item.RawString, prop.PropertyType));
            }
        }
    }

    /// <summary>
    /// 递归获取并处理占位符的值。
    /// </summary>
    /// <param name="name">占位符名称。</param>
    /// <returns>替换后的字符串，如果不存在则为 <c>null</c>。</returns>
    private string? GetParsedValue(string name)
    {
        if (!_data.TryGetValue(name, out var item))
        {
            return null;
        }
        if (item.Value is not null)
        {
            return item.Value;
        }
        var value = item.RawString;
        value = value.ReplacePlaceholders(GetParsedValue);
        if (item.Parser is not null)
        {
            value = Helper.Sync(() => item.Parser(value));
        }
        value = value.ReplacePlaceholders(GetParsedValue);
        _data[name] = item with { Value = value };
        return value;
    }

    /// <summary>
    /// 存储额外数据的字典，键为字符串，值为 DataValue 结构体。
    /// </summary>
    private readonly Dictionary<string, DataValue> _data = new(VariableNameComparer.Instance);

    /// <summary>
    /// 通过键名获取或设置对应的值。如果键名不存在，返回 null；如果设置的值为 null，则移除该键值对。
    /// </summary>
    /// <param name="name"> 键名。</param>
    /// <exception cref="InvalidOperationException"> 当尝试修改只读属性时抛出。</exception>
    public string? this[string name]
    {
        get => _data.TryGetValue(name, out var value) ? value.Value : null;
        set
        {
            if (_data.TryGetValue(name, out var data))
            {
                if (data.IsReadOnly)
                {
                    throw new InvalidOperationException($"'{name}' 是只读的，不能修改。");
                }
                if (value == null)
                {
                    _data.Remove(name);
                    return;
                }
                _data[name] = data with
                {
                    RawString = value,
                    Value = value.ReplacePlaceholders(GetParsedValue)
                };
            }
            else if (value != null)
            {
                data = new DataValue(name, value)
                {
                    Value = value.ReplacePlaceholders(GetParsedValue)
                };
            }
        }
    }

    /// <summary>
    /// 将当前对象的所有公共属性值刷新到内部数据字典中
    /// </summary>
    public void Flush()
    {
        foreach (var prop in this.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
        {
            if (prop.GetIndexParameters().Length > 0 || prop.SetMethod is null or { IsPublic: false })
            {
                continue;
            }

            var value = prop.GetValue(this)?.ToString() ?? string.Empty;

            if (_data.TryGetValue(prop.Name, out var item))
            {
                if (item.IsReadOnly)
                {
                    throw new InvalidOperationException($"'{prop.Name}' 是只读的，不能修改。");
                }
                if (item.RawString == value)
                {
                    continue;
                }
                item = item with
                {
                    RawString = value,
                    Value = value.ReplacePlaceholders(GetParsedValue)
                };
            }
            else
            {
                item = new DataValue(prop.Name, value)
                {
                    Value = value.ReplacePlaceholders(GetParsedValue),
                    Description = prop.GetCustomAttribute<DescriptionAttribute>()?.Description
                };
            }
            _data[item.Name] = item;
            UpdateTime = DateTime.Now;
        }

    }

    /// <inheritdoc />
    IEnumerator<KeyValuePair<string, string>> IEnumerable<KeyValuePair<string, string>>.GetEnumerator()
    {
        foreach (var (name, value) in _data)
        {
            yield return new(name, value.Value ?? GetParsedValue(name)!);
        }
    }

    /// <inheritdoc />
    IEnumerator IEnumerable.GetEnumerator()
        => ((IEnumerable<KeyValuePair<string, string>>)this).GetEnumerator();
}
